// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------

namespace System.Fabric.Test.Common.Serialization
{
    using Newtonsoft.Json.Linq;
    using System.Diagnostics;
    using System.Fabric.Common.Serialization;
    using System.Fabric.JsonSerializerWrapper;
    using System.IO;
    using System.Text;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    internal class JsonSerializationInteropTest
    {
        private const string nativeSerializerExeName = @"NativeAndManagedSerializationInteropTest.exe";
        private const string jsonFile1Name = "SerializerUnitTestsTempfile_1managed.txt";
        private const string jsonFile2Name = "SerializerUnitTestsTempfile_2native.txt";

        private const string TestDllNamePrefix = "System.Fabric.Test.Common.Serialization";
        private const string TestCaseNameEnvVar = @"%WinFabric_TestName%";
        private const string TestCaseDataRootEnvVar = @"%WinFabric_TestCaseDataRoot%";

        private static readonly string nativeExeBinDir = Environment.ExpandEnvironmentVariables(@"%_NTTREE%\FabricUnitTests");
        private string nativeSerializerExePath = Environment.ExpandEnvironmentVariables(Path.Combine(nativeExeBinDir, nativeSerializerExeName));

        private string dataDir;
        private string jsonFile1ManagedPath;
        private string jsonFile2NativePath;

        IJsonSerializer Serializer;
        Random random;

        public JsonSerializationInteropTest()
        {
            this.Serializer = JsonSerializerImplDllLoader.TryGetJsonSerializerImpl();
            if (this.Serializer == null)
            {
                throw new DllNotFoundException("SerializerUnitTests..ctor(): Call to JsonSerializerImplDllLoader.TryGetJsonSerializerImpl() failed.");
            }

            this.random = new Random((int)DateTime.Now.Ticks);

            this.dataDir = nativeExeBinDir;

            // If WinFabric_TestName pertains to this test and DataRoot is set then use WinFabric_TestCaseDataRoot.
            if (Environment.ExpandEnvironmentVariables(TestCaseNameEnvVar).ToLower().Contains(TestDllNamePrefix.ToLower()))
            {
                string testCaseDataDir = Environment.ExpandEnvironmentVariables(TestCaseDataRootEnvVar);
                if(Directory.Exists(testCaseDataDir))
                {
                    this.dataDir = testCaseDataDir;
                }
            }

            this.jsonFile1ManagedPath = Path.GetFullPath(Path.Combine(dataDir, jsonFile1Name));
            this.jsonFile2NativePath = Path.GetFullPath(Path.Combine(dataDir, jsonFile2Name));
        }

        [TestMethod]
        [TestProperty("ThreadingModel", "MTA")]
        public void DescriptionInteropTest()
        {
            var item1Managed = new DescriptionSerializationInteropTest();
            item1Managed.Init();

            this.InteropTest(item1Managed, this.jsonFile1ManagedPath, this.jsonFile2NativePath, "DescriptionSerializationInteropTest");
        }

        [TestMethod]
        [TestProperty("ThreadingModel", "MTA")]
        public void HealthInteropTest()
        {
            var item1Managed = new HealthSerializationInteropTest();
            item1Managed.Init();

            this.InteropTest(item1Managed, this.jsonFile1ManagedPath, this.jsonFile2NativePath, "HealthSerializationInteropTest");
        }

        [TestMethod]
        [TestProperty("ThreadingModel", "MTA")]
        public void QueryInteropTest()
        {
            var item1Managed = new QuerySerializationInteropTest();
            item1Managed.Init();

            this.InteropTest(item1Managed, this.jsonFile1ManagedPath, this.jsonFile2NativePath, "QuerySerializationInteropTest");
        }

        // Serialize managed item to file1, calls native serializer
        // Compare item deserialized from native json
        private void InteropTest<T>(T item1, string filepath1, string filepath2, string testName)
        {
            Assert.IsNotNull(item1, "Assert item1 is not null.");
            string json1 = this.Serializer.Serialize(item1);
            Assert.IsNotNull(json1, "Assert json1 is not null. json1: " + json1);

            // Write json to file for native serializer to read.
            WriteJsonToFile(item1, filepath1);

            // Read json produced by native serializer from filepath2.
            this.CallNativeSerializer(filepath1, filepath2, testName);

            // Read json file generated by native serializer.
            string json2 = this.ReadJsonFromFile(filepath2);
            Assert.IsNotNull(json2, "Assert json2 is not null. json2: " + json2);
            var jobject2 = (JObject)this.Serializer.Deserialize(json2, typeof(JObject));
            var jobject1 = (JObject)this.Serializer.Deserialize(json1, typeof(JObject));
            var diffs = SerializationUtil.CompareJsonToken(jobject1, jobject2, "root");
            diffs.ForEach(s => Assert.IsNotNull(s, "differences: " + s));

            var item1Managed = (T) this.Serializer.Deserialize(json1, typeof(T));
            var item2native = (T) this.Serializer.Deserialize(json2, typeof(T));

            Assert.IsNotNull(item2native, "deserialized object 'item2native' is not null");
            Assert.IsNotNull(item2native, this.Serializer.Serialize(item2native));

            bool matches = SerializationUtil.ObjectMatches(item1Managed, item2native, "ManagedNativeCompatibilityTestObject");
            Assert.IsTrue(matches, "compare two object instances.");
        }

        private void CallNativeSerializer(string filepathIn, string filepathOut, string testName)
        {
            Assert.IsNotNull(string.IsNullOrWhiteSpace(filepathIn), "Assert filepath1 is not null: " + filepathIn);
            Assert.IsNotNull(string.IsNullOrWhiteSpace(filepathOut), "Assert filepath2 is not null: " + filepathOut);

            // CallNativeSerializer executable which reads JSON from one file and writes to another.
            string args = testName + " " + filepathIn + " " + filepathOut;
            Assert.IsNotNull(string.IsNullOrWhiteSpace(args), "Assert filepath1 is not null: " + args);

            var p = Process.Start(this.nativeSerializerExePath, args);
            p.WaitForExit();
            Assert.IsTrue(p.ExitCode == 0, "Assert nativeSerializer exited with success.");
        }

        private void WriteJsonToFile(object item, string filePath)
        {
            // Write json to filepath1
            using (FileStream fw = new FileStream(filePath, FileMode.Create, FileAccess.Write))
            {
                this.Serializer.Serialize(item, fw);
            }
        }

        private string ReadJsonFromFile(string filePath)
        {
            // Read native json from filepath2
            string json2 = string.Empty;
            using (StreamReader streamReader = new StreamReader(filePath, Encoding.UTF8))
            {
                json2 = streamReader.ReadToEnd();
            }

            return json2;
        }
    }
}